package org.jinghouyu.json.beans;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

import org.jinghouyu.json.beans.KeyValueDescriptor.KeyValue;
import org.jinghouyu.json.core.JsonException;
import org.jinghouyu.json.utils.ClassLoaderUtils;
import org.jinghouyu.json.utils.TypeUtils;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;

public class JavaBeanASMBuilder implements Opcodes {
	private static final Map<Class<?>, KeyValueDescriptor<Object>> map = new HashMap<Class<?>, KeyValueDescriptor<Object>>();
	private static AtomicInteger seed = new AtomicInteger(0);
	
	public static KeyValueDescriptor<Object> build(Object value) {
		Class<?> beanClass = value.getClass();
		if(map.get(beanClass) == null) {
			synchronized(JavaBeanASMBuilder.class) {
				if(map.get(beanClass) == null) {
					KeyValueDescriptor<Object> serializer = createJavaBeanSerializer(value);
					map.put(beanClass, serializer);
				}
			}
		}
		return map.get(beanClass);
	}
	
	public static void main(String[] args) {
		Object data = new Date();
		KeyValueDescriptor<Object> s = build(data);
		for(KeyValue<String, Object> item : s.listItem(data)) {
			System.out.println(item.getKey() + ":" + item.getValue());
		}
	}
	
	private static KeyValueDescriptor<Object> createJavaBeanSerializer(Object bean) {
		Class<?> beanClass = bean.getClass();
		ClassWriter cw = new ClassWriter(0);
		Class<?> superClass = KeyValueDescriptor.class;
		String beanClassPath = beanClass.getName().replaceAll("\\.", "/");
		String currentClassName = superClass.getPackage().getName() + "." + beanClass.getSimpleName() + "_" + seed.addAndGet(1);
		String classASMName = superClass.getName().replaceAll("\\.", "/");
		//获取当前JDK版本
		cw.visit(V1_6, ACC_PUBLIC, currentClassName.replaceAll("\\.", "/"), null, getClassPath(Object.class), new String[]{classASMName});
		
		//初始化构造器
		MethodVisitor cmv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
		cmv.visitVarInsn(ALOAD, 0);
		cmv.visitMethodInsn(INVOKESPECIAL, getClassPath(Object.class), "<init>", "()V");
		cmv.visitInsn(RETURN);
		cmv.visitMaxs(1, 1);
		cmv.visitEnd();
		MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "listItem", "("+getClassCode(Object.class)+")"+getClassCode(List.class), null, null);
		ClassDescriptor classDes = ClassDescriptor.getClassDescriptor(beanClass);
		PropertyDescriptor[] properties = classDes.getPropertyDescriptors();
		
		mv.visitVarInsn(ALOAD, 1);
		mv.visitVarInsn(ASTORE, 0);
		
		mv.visitTypeInsn(NEW, getClassPath(ArrayList.class));
		mv.visitInsn(DUP);
		mv.visitMethodInsn(INVOKESPECIAL, getClassPath(ArrayList.class), "<init>", "()V");

		for(PropertyDescriptor p : properties) {
			String key = p.getName();
			Method method = p.getReadMethod();
			if(method == null) {
				continue;
			}
			Class<?> fieldClass = method.getReturnType();
			String methodName = method.getName();
			mv.visitInsn(DUP);
			mv.visitTypeInsn(NEW, getClassPath(KeyValue.class));
			mv.visitInsn(DUP);
			mv.visitLdcInsn(key);
			mv.visitVarInsn(ALOAD, 0);
			mv.visitInsn(DUP);
			mv.visitVarInsn(ASTORE, 0);
			mv.visitTypeInsn(CHECKCAST, beanClassPath);
			mv.visitMethodInsn(INVOKEVIRTUAL, beanClassPath, methodName, "()" + getClassCode(method.getReturnType()));
			if(fieldClass.isPrimitive()) {//判断是否是基本类型，基本类型还需要将其装换为.....
				mv.visitMethodInsn(INVOKESTATIC, getClassPath(TypeUtils.getPackedClass(fieldClass)), "valueOf", "(" + getClassCode(fieldClass)+ ")" + getClassCode(TypeUtils.getPackedClass(fieldClass)));
			}
			
			if(fieldClass.isPrimitive()) {
				mv.visitFieldInsn(GETSTATIC, getClassPath(TypeUtils.getPackedClass(fieldClass)), "TYPE", "Ljava/lang/Class;");
			} else {
				mv.visitLdcInsn(Type.getType(getClassCode(method.getReturnType())));
			}
			mv.visitMethodInsn(INVOKESPECIAL, getClassPath(KeyValue.class), "<init>", "("+getClassCode(Object.class)+getClassCode(Object.class)+getClassCode(Class.class)+")V");
			mv.visitMethodInsn(INVOKEVIRTUAL, getClassPath(ArrayList.class), "add", "("+getClassCode(Object.class)+")"+getClassCode(boolean.class));
			mv.visitInsn(POP);
		}
		
		mv.visitInsn(ARETURN);
		mv.visitMaxs(7, 2);
		mv.visitEnd();
		cw.visitEnd();
		byte[] bs = cw.toByteArray();
		Class<?> clazz = ClassLoaderUtils.defineClass(currentClassName, bs);
		try {
			return (KeyValueDescriptor<Object>)clazz.newInstance();
		} catch (Exception e) {
			throw new JsonException(e);
		}
	}
	
	private static Map<Class<?>, String> classCodeMap = new HashMap<Class<?>, String>();
	static {
		classCodeMap.put(int.class, "I");
		classCodeMap.put(short.class, "S");
		classCodeMap.put(boolean.class, "Z");
		classCodeMap.put(byte.class, "B");
		classCodeMap.put(char.class, "C");
		classCodeMap.put(float.class, "F");
		classCodeMap.put(long.class, "J");
		classCodeMap.put(double.class, "D");
	}
	
	private static String getClassPath(Class<?> clazz) {
		return clazz.getName().replaceAll("\\.", "/");
	}
	
	private static String getClassCode(Class<?> clazz) {
		String classCode = classCodeMap.get(clazz);
		if(classCode != null) return classCode;
		if(clazz.isArray()) {
			StringBuilder sb = new StringBuilder();
			sb.append('[');
			sb.append(getClassCode(clazz.getDeclaringClass()));
			return sb.toString();
		} else {
			return 'L' + getClassPath(clazz) + ';';
		}
	}
}
